* feat(core): express grate constancy in the bundle, on top of #127
Rebased onto `52671355` (the optic-class answer: `Z = Xo`,
`Function1BroadcastOptic`, `RepresentativeIndex`, the positional write).
That commit and this one answer different questions, and this rebase keeps
upstream's machinery — and upstream's *idiom* — wherever it is better:
- **kept** `Function1BroadcastOptic` as the kernel's per-index write hook
(one value per position, no bundle read it did not build) and the
bridge's `RepresentativeIndex[X0]` witness;
- **added** the sum: the focus half is `F[A] | Broadcast[F, A]` (both cases
`private[eo]`), with `MultiFocus.broadcast` / `.broadcast` as the public
handles and `.foci` total for both (the index-free case carries its `F[A]`
image);
- **added** the kernel's remaining cases: a composite inner is written per
position with the leftover *its own* read produced (upstream forges it, so
a `fromLensF` inner NPEs), and a tabulating ∘ tabulating composite writes
the exact inverse of its diagonal read. That last one needs `C =:= D`,
which is a property of the *optic* — so it rides on the optic as
`Optic.SameFocus` (mixed into every `andThen` composite from the call
site's `summonFrom`, and into the monomorphic factories
`representable` / `tuple` / `apply` with `Some(…)`), and the kernel reads
it off the `inner` it is already handed. `AssociativeFunctor` and the
composition algebra are untouched (zero diff vs upstream).
- **deleted `unobserved`** — no write needs a stand-in any more. Its three
historical sites are fixed at the source: the kernel records the inner
leftovers its read observed, the bridge takes a real index witness, and
the shape-collapsing `collectList` now hands the write the leftover its
own read produced (`o.from((bundle.context, List(agg(bundle.foci))))`),
which fixes a live NPE / `MatchError` on Lens / Prism / Optional
provenances (probed before the fix).
New coverage: `tests/.../MultiFocusCrossFamilySpec.scala` is the runtime
twin of `CompositionMatrixSpec` — nine property blocks over every inbound
provenance (generic factory, polymorphic factory, Iso/Lens/Prism/Optional
bridges, a composite of two shipped optics) through `modify` / `replace` /
`collectMap` / `collectWith` / `collectList` / `foldMap` / `headOption` /
`length`, plus both composition directions across Iso / Lens / Prism /
Optional with independently computed expectations. The collapsing
aggregate is asserted only where the optic's leftover is count-agnostic; a
composite's is the one named boundary (see the note).
Measured against `52671355` (throwaway printing spec, removed again):
`tuple ∘ tuple.modify(identity)` on `((1,2),(3,4))` → `((1,4),(1,4))` there,
identity here; `replace(9)` → `((9,9),(9,9))` there, `((9,2),(3,9))` here;
`tuple ∘ (shim ∘ shim).modify(_+1)` on `(1,2)` → `(2,2)` there, `(2,3)`
here; a `fromLensF` inner NPEs there; `collectList` NPEs / MatchErrors there
and works here.
Upstream's own `MultiFocusFunction1Spec` is kept verbatim (14 examples /
349 expectations) and passes against this kernel; this branch's core
`MultiFocusFunction1CompositionSpec` adds 13 blocks / 1003 expectations and
`external/EoOpaqueSurfaceSpec` pins the public surface from outside the
`eo` package tree. Gates (sbt 1.13.0, JDK 25): root `test` 237 examples /
0 failures, `scalafmtCheckAll`, `scalafixAll --check`, `scalafmtSbtCheck`,
`benchmarks/scalafmtCheck`, `mimaReportBinaryIssues`, `githubWorkflowCheck`,
`docs/mdoc` 0 errors, `docs/laikaSite`, `core/doc`.
* build(deps): land #118 + #124 + #126 as one verified bump; fix avro's stryker blocker (#115) (#128)
* build(deps): bump actions/cache from v4 to v6
Supersedes #118. Drop-in verified: action.yml inputs are byte-identical
between v4.3.0 and v6.1.0 (only the node20 -> node24 runtime line
differs); all 18 steps pass exactly {path, key, restore-keys}; @v6
resolves to v6.1.0, which adds graceful handling of read-only cache
tokens (the accurate 'cache write denied' warning on fork PRs instead
of the bogus 'another job may be creating this cache').
* build(deps): bump droste-core from 0.9.0-M3 to 0.10.0
Supersedes #124. Benchmark-only dependency (never published downstream).
0.10.0 is dependency maintenance upstream (cats update,
scala-collection-compat 2.13.0, Scala.js 1.18.2); our droste surface
(Fix, scheme.{cata,ana,hylo}, Algebra/Coalgebra) compiles unchanged and
all nine SchemesBench benchmarks execute on the new version.
* build(deps): bump sbt-stryker4s from 0.20.4 to 1.1.1
Supersedes #126. No removed or renamed features reach this build: the
setting keys used in build.sbt (strykerReporters, strykerExcludedMutations,
strykerThresholdsBreak) are unchanged, the borrowed-tests wiring still
resolves, and 1.x requires sbt >= 1.11.2 (we run 1.13.0). Verified with a
schemes smoke run (48/58 killed, 0 NoCoverage) and a full avroIntegration
run (481 killed / 51 survived / 58 known-macro NoCoverage, no crash).
Decisive for issue #115: 1.1.1 fixes stryker4s' rollback invariant crash
('cases should be non-empty') that killed avro's mutation run on main —
0.20.4's mutant removal could empty a Term.Match by deleting its default
Pat.Wildcard case; 1.1.1 filters it. Also refreshes the invocation-doctrine
comment: the module-scoped <m>/stryker form works again since 0.20.4.
* fix(avro): keep AvroWalk's null-narrowing outside stryker's reach
Addresses #115 blocker 1. The flow-typed 'val here = if index != null
then index else ...' loses its narrowing the moment stryker4s' mutator
rewrites the condition, so two mutants died as compile errors instead of
being exercised (and on 0.20.4 that rollback crashed the whole run).
Express the narrowing as a match on the null sentinel with an explicit
JMap[String, Integer] type: a match has no condition to mutate, so the
narrowing is structural rather than flow-based. (The ascribe + 'index.nn'
spelling from the issue does not compile here — the guard already narrows,
E216 fires, and -Werror rejects the warning.) Applied at both occurrences
(totalNominalIndex and recordSlots); AvroCompileError mutants from
AvroWalk go to zero.
* docs: refresh version pins and the stryker invocation doctrine
CLAUDE.md said Scala 3.8.3 / sbt 1.12.9; the project builds Scala 3.9.0
(build.sbt scala3Version) on sbt 1.13.0 (project/build.properties).
CONTRIBUTING.md's bootstrap list pointed Scala at build.properties and
omitted the JDK 25 requirement for kyo + the docs site.
The 'invoke as project <m>; stryker, NOT <m>/stryker' claim stopped being
true in 0.20.4 (module-scoped task resolution was fixed upstream); state
the history instead of forbidding the working form. The mutationAll alias
also covers zio and kyo now, not just the original eight modules.
quality-assurance.md's scoverage path follows the Scala version.
* build(ci): bump the workflow generator's actions/cache ref to v6
The cache bump landed as a hand-edit of the generated ci.yml (mirroring
Dependabot's #118 diff), which githubWorkflowCheck rightly flags: ci.yml
is generator-owned (CONTRIBUTING.md: never hand-edit it). This pairs the
edit with its source of truth — UseRef.Public("actions", "cache", "v6")
in the setup-java cache patch. githubWorkflowGenerate now emits zero
workflow drift, so the committed ci.yml is exactly generated output, and
githubWorkflowCheck passes.
* docs(benchmarks): sweep 462e037 (2026-10-03) [skip ci]
* feat(core) PROTOTYPE: IndexedGlass — product-indexed split/rebuild optic (full-grid composition)
Standalone spike for the grate re-design (docs/research/2026-10-05-multifocus-redesign.md
SS5 phase 1). Composition yields index (I, J) and context (Ctx, I => inner.Ctx): no
C =:= D evidence, no index-== addressing, no SameFocus-style witness. Grate alias =
Context = Unit case (not yet composition-closed — normalization is an open item).
GlassSpec: 10 examples / 505 expectations covering arbitrary tabulations, 3-level
associativity, type-changing composition through a non-inline generic helper,
permuted/empty/Unit/NaN/signed-zero indexes, and the explicit full-grid vs
diagonal semantic change.
* refactor(core): retire legacy MultiFocus Grate routing
* refactor(core): move fixed-index optics to Indexed and add usage examples
* feat(core): distinguish Grate and PGrate and enrich Indexed examples
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Bumps io.higherkindness:droste-core_3 from 0.9.0-M3 to 0.10.0.
Release notes
Sourced from io.higherkindness:droste-core_3's releases.
... (truncated)
Changelog
Sourced from io.higherkindness:droste-core_3's changelog.
Commits
3459d57Updates dependencies (#468)97642c0Update scala-collection-compat to 2.13.0d565befUpdate mdoc, sbt-mdoc to 2.6.45f067efUpdate mdoc, sbt-mdoc to 2.6.3bf70e55Update sbt-scalajs, scalajs-compiler, ... to 1.18.26258678Update scalafmt-core to 3.8.6357e5a5Update scalafmt-core to 3.8.512faadcUpdate scalafmt-core to 3.8.462a5e59Update sbt-scalajs, scalajs-compiler, ... to 1.18.1ad9aee0Update sbt-ci-release to 1.9.2Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)